package com.linkhub.oms.component.redis.locker.impl;

import com.google.common.collect.Lists;
import com.linkhub.oms.common.annotation.AfterCompletion;
import com.linkhub.oms.common.constant.WmsInfoConstant;
import com.linkhub.oms.common.exception.BizException;
import com.linkhub.oms.common.util.QtStreamUtil;
import com.linkhub.oms.component.redis.locker.DistributedLocker;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

/**
 * @Description: RedissonDistributedLocker
 * @Author: echo
 * @Date: 2024/3/3 11:29
 * @Version: 1.0
 */
@Slf4j
public class RedissonDistributedLocker implements DistributedLocker {

    private RedissonClient redissonClient;

    public RedissonDistributedLocker(RedissonClient redissonClient){
        this.redissonClient = redissonClient;
    }

    @Override
    public RLock lock(String lockKey) {
        log.info("RedissonDistributedLocker lock: {}", lockKey);
        RLock lock = redissonClient.getLock(lockKey);
        lock.lock();
        return lock;
    }

    @Override
    public RLock lock(String lockKey, int timeout) {
        log.info("RedissonDistributedLocker lock: {}", lockKey);
        RLock lock = redissonClient.getLock(lockKey);
        lock.lock(timeout, TimeUnit.SECONDS);
        return lock;
    }

    @Override
    public RLock lock(String lockKey, int timeout, TimeUnit unit) {
        log.info("RedissonDistributedLocker lock: {}", lockKey);
        RLock lock = redissonClient.getLock(lockKey);
        lock.lock(timeout, unit);
        return lock;
    }

    @Override
    public boolean tryLock(String lockKey) {
        log.info("RedissonDistributedLocker tryLock: {}", lockKey);
        RLock lock = redissonClient.getLock(lockKey);
        return lock.tryLock();
    }

    @Override
    public boolean tryLock(String lockKey, int waitTime, int leaseTime, TimeUnit unit) {
        log.info("RedissonDistributedLocker tryLock: {}", lockKey);
        RLock lock = redissonClient.getLock(lockKey);
        try {
            return lock.tryLock(waitTime, leaseTime, unit);
        } catch (InterruptedException e) {
            return false;
        }
    }

    /**
     * 尝试获取锁，返回获取成功的lockKey列表，且在指定时间后自动释放锁
     *
     * @param lockKeysMap 包含需要尝试获取的lockKey及其对应的实际锁Key的Map
     * @return 获取成功的lockKey列表
     */
    @Override
    public List<String> tryWmsLock( Map<String, String> lockKeysMap ) {
        return tryLock(lockKeysMap, WmsInfoConstant.WMS_API_RETRY_INTERVAL);
    }

    /**
     * 尝试获取锁，返回获取成功的lockKey列表，且在指定时间后自动释放锁
     *
     * @param lockKeysMap 包含需要尝试获取的lockKey及其对应的实际锁Key的Map
     * @param releaseTime 锁自动释放时间（分钟）
     * @return 获取成功的lockKey列表
     */
    @Override
    public List<String> tryLock( Map<String, String> lockKeysMap, int releaseTime ) {
        List<String> successfulLockKeys = new ArrayList<>();

        for (Map.Entry<String, String> entry : lockKeysMap.entrySet()) {
            String key = entry.getKey(); // 原始的lockKey
            String actualLockKey = entry.getValue(); // 用于获取锁的实际key

            RLock lock = redissonClient.getLock(actualLockKey);
            try {
                // 尝试获取锁并设置自动释放时间
                boolean isLocked = lock.tryLock(0, releaseTime, TimeUnit.MINUTES);

                if (isLocked) {
                    successfulLockKeys.add(key);
                    log.info("Successfully locked: key:{}, value:{}", key, actualLockKey);
                } else {
                    log.info("Failed to lock: key:{}, value:{}", key, actualLockKey);
                }
            } catch (InterruptedException e) {
                log.error("Error trying to lock: key:{}, value:{}", key, actualLockKey, e);
                Thread.currentThread().interrupt();
            }
        }

        return successfulLockKeys;
    }

    @Override
    @AfterCompletion(async = false)
    public void unlock(String lockKey) {
        RLock lock = redissonClient.getLock(lockKey);
        // 当前线程持有锁才释放，否则会报错
        if (lock.isLocked() && lock.isHeldByCurrentThread()) {
            lock.unlock();
            log.info("RedissonDistributedLocker unlock: {}", lockKey);

        }
    }

    @Override
    @AfterCompletion(async = false)
    public void unlock(RLock lock) {
        if (lock.isLocked() && lock.isHeldByCurrentThread()) {
        log.info("RedissonDistributedLocker unlock: {}", lock.getName());
            lock.unlock();
        }
    }


    @Override
    public RLock multiLock(List<String> lockKeys) {
        RLock lock = getMultiLock(lockKeys, "RedissonDistributedMultiLocker, lock: ");
        lock.lock();
        return lock;
    }

    @Override
    public RLock multiLock(List<String> lockKeys, int timeout) {
        RLock lock = getMultiLock(lockKeys, "RedissonDistributedMultiLocker, lock: ");
        lock.lock(timeout,TimeUnit.SECONDS);
        return lock;
    }

    @Override
    public RLock multiLock(List<String> lockKeys, int timeout, TimeUnit unit) {
        RLock lock = getMultiLock(lockKeys, "RedissonDistributedMultiLocker, lock: ");
        lock.lock(timeout,unit);
        return lock;
    }

    @Override
    public boolean tryLockMulti(List<String> lockKeys) {
        RLock lock = getMultiLock(lockKeys, "RedissonDistributedMultiLocker, tryLock: ");
        return lock.tryLock();
    }

    @Override
    public boolean tryLockMulti(List<String> lockKeys, int waitTime, int leaseTime, TimeUnit unit) {
        RLock lock = getMultiLock(lockKeys, "RedissonDistributedMultiLocker, tryLock: ");
        try {
            return lock.tryLock(waitTime, leaseTime, unit);
        } catch (InterruptedException e) {
            return false;
        }
    }

    @Override
    @AfterCompletion(async = false)
    public void unLockMulti(List<String> lockKeys) {
        RLock lock = getMultiLock(lockKeys, "RedissonDistributedMultiLocker, unLock: ");
        lock.unlock();

    }

    /**
     * 获取联合锁
     *
     * @param lockKeys 锁的键值s
     * @return 联合锁
     */
    private RLock getMultiLock(List<String> lockKeys, String logPrefix) {
        if (CollectionUtils.isEmpty(lockKeys)) {
            throw new BizException("lockKeys must not empty !");
        }

        lockKeys = QtStreamUtil.distinct(lockKeys, Function.identity());
        lockKeys.sort(String::compareTo);
        log.info(logPrefix + lockKeys);

        List<RLock> locks = Lists.newArrayListWithCapacity(lockKeys.size());
        lockKeys.forEach(lockKey -> {
            RLock lock = redissonClient.getLock(lockKey);
            locks.add(lock);
        });
        return redissonClient.getMultiLock(locks.toArray(new RLock[0]));
    }

}
